
#include <BPainter>
#include <BKeyEvent>
#include <BMouseEvent>

#include "member_BScrollBar.h"
#include "member_BWidget.h"
#include "member_BStyle.h"

using namespace BWE;

#define member					(*(member_BScrollBar*)_ptr)
#define member_allocate()		_ptr = new member_BScrollBar(this)
#define member_release()		delete (member_BScrollBar*)_ptr

BScrollBar::BScrollBar(BWidget* parent) : BWidget(parent)
{
	member_allocate();
}
BScrollBar::BScrollBar(const BString& name, BWidget* parent) : BWidget(name, parent)
{
	member_allocate();
}
BScrollBar::BScrollBar(Orientation orientation, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.orientation = orientation;
}
BScrollBar::BScrollBar(int minimum, int maximum, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.range.set(minimum, maximum);
}
BScrollBar::BScrollBar(int minimum, int maximum, Orientation orientation, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.range.set(minimum, maximum);
	member.orientation = orientation;
}
BScrollBar::BScrollBar(const BRange& range, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.range = range;
}
BScrollBar::BScrollBar(const BRange& range, Orientation orientation, BWidget* parent) : BWidget(parent)
{
	member_allocate();
	member.range = range;
	member.orientation = orientation;
}
BScrollBar::~BScrollBar()
{
	member_release();
}

void BScrollBar::setOrientation(Orientation orientation)
{
	if (member.orientation != orientation)
	{
		member.orientation = orientation;
		if (member.increaseButton)
		{
			if (member.orientation & Orientation_Horizontal)
			{
				member.increaseButton->setGraph(Graph_Arrow_Left_Fill);
				member.decreaseButton->setGraph(Graph_Arrow_Right_Fill);
			}
			if (member.orientation & Orientation_Vertical)
			{
				member.increaseButton->setGraph(Graph_Arrow_Up_Fill);
				member.decreaseButton->setGraph(Graph_Arrow_Down_Fill);
			}
		}
		member.dirty = true;
		this->emit(Signal_OrientationChanged, orientation);
		this->fresh();
	}
}
Orientation BScrollBar::orientation() const
{
	return member.orientation;
}

bool BScrollBar::setValue(int value)
{
	BRange range(member.range.min(), member.range.max() - member.page);
	value = range.limit(value);
	if (member.value != value)
	{
		member.value = value;
		emit(Signal_ValueChanged, member.value);
		member.dirty = true;
		this->fresh();
		return true;
	}
	return false;
}
int BScrollBar::value() const
{
	return member.value;
}

void BScrollBar::setPage(int page)
{
	page = bClamp(page, 10, member.range.size());
	if (member.page != page)
	{
		member.page = page;
		emit(Signal_PageChanged, member.page);
		member.dirty = true;
		this->fresh();
	}
}
int BScrollBar::page() const
{
	return member.page;
}

void BScrollBar::setMinimum(int minimum)
{
	setRange(BRange(minimum, member.range.max()));
}
int BScrollBar::minimum() const
{
	return member.range.min();
}

void BScrollBar::setMaximum(int maximum)
{
	setRange(BRange(member.range.min(), maximum));
}
int BScrollBar::maximum() const
{
	return member.range.max();
}

void BScrollBar::setRange(int minimum, int maximum)
{
	setRange(BRange(minimum, maximum));
}
void BScrollBar::setRange(const BRange& range)
{
	if (member.range != range)
	{
		member.range = range;
		member.dirty = true;
		emit(Signal_RangeChanged, member.range);
		this->fresh();
	}
}
const BRange& BScrollBar::range() const
{
	return member.range;
}

void BScrollBar::setStep(int step)
{
	if (step < 0)
		return;
	if (member.step != step)
	{
		member.step = step;
		emit(Signal_StepChanged, member.step);
	}
}
int BScrollBar::step() const
{
	return member.step;
}

void BScrollBar::increase()
{
	int value = member.value + member.step;
	this->setValue(value);
}
void BScrollBar::decrease()
{
	int value = member.value - member.step;
	this->setValue(value);
}

void BScrollBar::setArrowVisible(bool arrowVisible)
{
	if (!member.increaseButton && arrowVisible)
	{
		member.decreaseButton = new BButton(this);
		member.decreaseButton->setName("decrease");
		member.decreaseButton->setForm(Form_IconOnly);

		member.increaseButton = new BButton(this);
		member.increaseButton->setName("increase");
		member.increaseButton->setForm(Form_IconOnly);

		member.connect(member.decreaseButton, Signal_Clicked, &member_BScrollBar::slotButtonClicked);
		member.connect(member.increaseButton, Signal_Clicked, &member_BScrollBar::slotButtonClicked);

		if (member.orientation & Orientation_Horizontal)
		{
			member.decreaseButton->setGraph(Graph_Arrow_Left_Fill);
			member.increaseButton->setGraph(Graph_Arrow_Right_Fill);
		}
		if (member.orientation & Orientation_Vertical)
		{
			member.decreaseButton->setGraph(Graph_Arrow_Up_Fill);
			member.increaseButton->setGraph(Graph_Arrow_Down_Fill);
		}

		member.dirty = true;
		this->fresh();
		return;
	}
	if (member.increaseButton && !arrowVisible)
	{
		this->removeChild(member.decreaseButton);
		this->removeChild(member.increaseButton);
		member.dirty = true;
		this->fresh();
	}
}
bool BScrollBar::arrowVisible() const
{
	return member.increaseButton.valid();
}

void BScrollBar::setHandleSize(const BSize& handleSize)
{
	if (member.handleSize != handleSize)
	{
		member.handleSize = handleSize;
		member.dirty = true;
		this->adjust();
	}
}
const BSize& BScrollBar::handleSize() const
{
	return member.handleSize;
}

bool BScrollBar::query(const BString& text, BValue& value) const
{
	if (text == "handle-rect")
	{
		value = member.handleRect;
		return true;
	}
	return BWidget::query(text, value);
}

BSize BScrollBar::sizeHint() const
{
	BSize baseSize = member.handleSize;
	if (const BFont* font = this->realFont())
	{
		if (baseSize.width() < 1)
			baseSize.width() = font->size();
		if (baseSize.height() < 1)
			baseSize.height() = font->size();
	}
	if (member.orientation & Orientation_Horizontal)
		return this->preferSize(baseSize.width() * 10, baseSize.height());
	if (member.orientation & Orientation_Vertical)
		return this->preferSize(baseSize.width(), baseSize.height() * 10);
	return this->preferSize(baseSize);
}

void BScrollBar::resizeEvent(const BEvent& event)
{
	member.dirty = true;
	BWidget::resizeEvent(event);
}

void BScrollBar::styleEvent(const BEvent& event)
{
	BWidget::styleEvent(event);
	const BStyle* style = event.value();

	member.autoTop = style->value(Value_Perch_Top).empty();
	member.autoLeft = style->value(Value_Perch_Left).empty();
	member.autoRight = style->value(Value_Perch_Right).empty();
	member.autoBottom = style->value(Value_Perch_Bottom).empty();

	style_member(style)->assign(Value_Handle_Size, member.handleSize);
	style_member(style)->assign(Value_Orientation, member.orientation);

	const BFont* font = this->realFont();
	if (member.increaseButton && member.decreaseButton)
	{
		member.increaseButton->setIconSize(font->size() / 2);
		member.decreaseButton->setIconSize(font->size() / 2);
	}

	member.dirty = false;
}
void BScrollBar::freshEvent(const BEvent& event)
{
	if (member.dirty)
	{
		member.freshRects();
		member.dirty = false;
	}
	BWidget::freshEvent(event);
}
void BScrollBar::paintEvent(const BEvent& event)
{
	BPainter painter(this);
	painter.drawBackground();
	painter.drawForeground();

	State state = painter.state();
	if (member.pressed)
	{
		state |= State_Pressed;
	}
	else if (member.hoverHandle)
	{
		state |= State_Hovered;
	}
	if (const BImage* image = painter.image(Image_Handle, state))
	{
		int margin = bMin(image->width(), member.handleRect.height()) / 2;
		if (member.orientation & Orientation_Horizontal)
		{
			painter.drawImage(member.handleRect, image, BMargin(margin, 0, margin, 0));
		}
		if (member.orientation & Orientation_Vertical)
		{
			painter.drawImage(member.handleRect, image, BMargin(0, margin, 0, margin));
		}
	}
	else if (painter.setColor(Color_Handle, state))
	{
		if (member.handlePoints.size())
		{
			painter.fillTriangleFan(member.handlePoints);
		}
		else
		{
			painter.fillRect(member.handleRect);
		}
	}

	painter.drawPerch();
	painter.drawMargin();
	painter.drawBorder();
}

void BScrollBar::mouseEnterEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverHandle)
	{
		member.hoverHandle = false;
		this->fresh();
	}
	BWidget::mouseEnterEvent(mouseEvent);
}
void BScrollBar::mouseLeaveEvent(const BMouseEvent& mouseEvent)
{
	if (member.hoverHandle)
	{
		member.hoverHandle = false;
		this->fresh();
	}
	BWidget::mouseLeaveEvent(mouseEvent);
}

void BScrollBar::mousePressEvent(const BMouseEvent& mouseEvent)
{
	BPoint pos = mouseEvent.pos();
	const BRect& crect = this->centerRect();
	if (mouseEvent.button() == Button_Left)
	{
		member.pressed = true;
		member.pressPos = pos;
		member.draging = false;
		if (member.handleRect.contain(pos))
		{
			member.pressValue = member.value;
			member.draging = true;
		}
		if (!member.draging)
		{
			int value = member.value;
			int pressValue = value;
			if (member.orientation & Orientation_Horizontal)
			{
				pressValue = member.range.value((double)(pos.x() - crect.x()) / crect.width());
			}
			if (member.orientation & Orientation_Vertical)
			{
				pressValue = member.range.value((double)(pos.y() - crect.y()) / crect.height());
			}
			int newValue = (value + pressValue) / 2;
			if (newValue > value && newValue < value + member.page)
				newValue = value + member.page;
			if (newValue < value && newValue > value - member.page)
				newValue = value - member.page;
			value = newValue;
			if (this->setValue(value))
			{
				emit(Signal_Scrolling, value);
			}
		}
		this->fresh();
	}
	BWidget::mousePressEvent(mouseEvent);
}
void BScrollBar::mouseReleaseEvent(const BMouseEvent& mouseEvent)
{
	if (mouseEvent.button() == Button_Left)
	{
		member.pressed = false;
		if (member.draging)
		{
			member.draging = false;
			this->emit(Signal_ScrollFinished, member.value);
		}
		this->fresh();
	}
	BWidget::mouseReleaseEvent(mouseEvent);
}
void BScrollBar::mouseMoveEvent(const BMouseEvent& mouseEvent)
{
	BPoint pos = mouseEvent.pos();
	const BRect& crect = this->centerRect();
	int rangeSize = member.range.size();
	if (member.draging && rangeSize > 0)
	{
		BPointf offset = pos - member.pressPos;
		int newValue = 0;
		if (member.orientation & Orientation_Horizontal)
		{
			if (member.orientation & Orientation_Reverse)
				newValue = int(member.pressValue - offset.x() / crect.width() * rangeSize);
			else
				newValue = int(member.pressValue + offset.x() / crect.width() * rangeSize);
		}
		if (member.orientation & Orientation_Vertical)
		{
			if (member.orientation & Orientation_Reverse)
				newValue = int(member.pressValue - offset.y() / crect.height() * rangeSize);
			else
				newValue = int(member.pressValue + offset.y() / crect.height() * rangeSize);
		}
		if (newValue < member.range.min())
			newValue = member.range.min();
		if (newValue > member.range.max() - member.page)
			newValue = member.range.max() - member.page;
		this->setValue(newValue);
		this->emit(Signal_Scrolling, member.value);
		this->fresh();
	}
	else
	{
		bool hoverHandle = member.handleRect.contain(pos);
		if (member.hoverHandle != hoverHandle)
		{
			member.hoverHandle = hoverHandle;
			this->fresh();
		}
	}
	BWidget::mouseMoveEvent(mouseEvent);
}
void BScrollBar::mouseWheelEvent(const BMouseEvent& mouseEvent)
{
	int whell = mouseEvent.wheel();
	if (setValue(member.value - whell * member.step))
	{
		this->emit(Signal_Scrolling, member.value);
		mouseEvent.accept();
	}
	BWidget::mouseWheelEvent(mouseEvent);
}

